package opdracht3;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Autoshow {

	private final static boolean SHOW_INFORMATION_PRINTS = false;

	private Lock lock;

	private int viewersInside = 0;
	private int viewersWaiting = 0;
	private int buyersWaiting = 0;
	private int subsequentBuyers = 0;

	private boolean buyerInside = false;
	private boolean subsequentBuyersReached = false;

	private Condition allowViewerInside, allowBuyerInside;

	public Autoshow() {
		lock = new ReentrantLock();

		allowViewerInside = lock.newCondition();
		allowBuyerInside = lock.newCondition();
	}

	/**
	 * Method that is called when a visitor wants to visit the autoshow.
	 * 
	 * @throws InterruptedException
	 */
	public void visit() throws InterruptedException {
		lock.lock();

		try {
			System.out.println(Thread.currentThread().getName()
					+ " wants to enter the show.");

			if (Thread.currentThread() instanceof Viewer) {
				// If the visitor is a viewer the amount of viewers waiting is
				// increased by one and decreased shortly after if viewers are
				// allowed inside. If they are not allowed inside they will be
				// told to wait. Waking up only when allowWieversInside is
				// signaled and viewersallowedInside results in true.
				viewersWaiting++;
				while (!viewerAllowedInside()) {
					allowViewerInside.await();
				}
				viewersWaiting--;

				// If the amount of viewers waiting to go inside is zero,
				// subsquentBuyersReached is set to false.
				if (viewersWaiting == 0) {
					subsequentBuyersReached = false;
				}
				if (SHOW_INFORMATION_PRINTS) {
					System.out
							.println("\t\t\tThe current value of subsequent buyers is "
									+ subsequentBuyersReached
									+ " And there are "
									+ viewersWaiting
									+ " viewers waiting.");
				}
				viewersInside++;
				System.out.println(Thread.currentThread().getName()
						+ " has entered the show.");
			} else {
				// If the visitor is not a viewer it is a buyer, the
				// buyersWaiting integer is increased by one and lowered after
				// it has been allowed to enter. If allowBuyersInside condition
				// is signaled and buyerCanEnter returns true it will pass and
				// let the buyer inside.
				buyersWaiting++;
				while (!buyerCanEnter()) {
					allowBuyerInside.await();
				}

				buyersWaiting--;
				buyerInside = true;
				System.out.println(Thread.currentThread().getName()
						+ " has entered the show.");
			}

		} finally {
			lock.unlock();
		}
	}

	/**
	 * Method called by a visitor to say he or she is leaving.
	 */
	public void leave() {
		lock.lock();
		try {
			System.out.println(Thread.currentThread().getName()
					+ " is leaving the show.");

			if (Thread.currentThread() instanceof Viewer) {
				// If the person leaving is a viewer, the amoutn of viewers
				// inside
				// is lowered, if the amount has reached zero the buyers are all
				// Signaled that they can enter.
				viewersInside--;
				if (viewersInside == 0) {
					allowBuyerInside.signalAll();
				}
			} else {
				// if it is a buyer leaving the subsequentBuyer interger is
				// increased by one and the buyerInside boolean is set to false.
				subsequentBuyers++;
				buyerInside = false;
				if (buyersWaiting == 0) {
					// If there are no more buyers waiting the subsequentbuyers
					// is set to zero and viewers are signaled that they can go
					// inside.
					subsequentBuyers = 0;
					allowViewerInside.signalAll();
				} else if (subsequentBuyers == 3) {
					// If subsequentBuyers is three the boolean is set to true
					// and viewers are signaled that they can go inside.
					System.out
							.println("Three subsequent buyers have entered, visitors are now allowed inside.");
					subsequentBuyersReached = true;
					subsequentBuyers = 0;
					allowViewerInside.signalAll();
				} else {
					// There are still buyers that can go inside, they are
					// signaled.
					allowBuyerInside.signalAll();
				}

			}
		} finally {
			lock.unlock();
		}
	}

	/**
	 * Method to determine if a buyer can enter the show. Returning true if no
	 * buyers are inside and the amount of viewers inside is zero. If this is
	 * not the case false is returned.
	 * 
	 * @return
	 */
	private boolean buyerCanEnter() {
		if (SHOW_INFORMATION_PRINTS) {
			System.out
					.println("\t\t\tBuyers cannot enter when a buyer is inside "
							+ buyerInside
							+ " when there are no viewers inside "
							+ viewersInside);
		}
		if (!buyerInside && viewersInside == 0) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Method that determines if a viewer is allowed to go inside. returning
	 * false if there is a buyerInside or there are buyers waiting. It returns
	 * true if more than 3 buyers have entered after one an another. or if there
	 * is no buyerinside and there are no buyers waiting.
	 * 
	 * @return
	 */
	private boolean viewerAllowedInside() {
		if (SHOW_INFORMATION_PRINTS) {
			System.out
					.println("\t\t\tViewers are allowed inside when subsequent buyers has been reached "
							+ subsequentBuyersReached
							+ " there are no buyers inside "
							+ buyerInside
							+ " or the amount of buyers waiting is zero "
							+ buyersWaiting);
		}
		if (subsequentBuyersReached) {
			return true;
		}
		if (buyerInside || buyersWaiting > 0) {
			return false;
		}
		return true;
	}
}
